﻿/* 
 * The MIT License
 * 
 * Copyright © 2010 WaterTrans
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 * 
 * Email: support@watertrans.com
 * 
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace WaterTrans.TypeLoader.Demo
{
    public partial class MainWindow : Window
    {
        private GlyphTypeface gtfEN;
        private GlyphTypeface gtfJP;

        private SingleGlyphConverter vert;
        private SingleGlyphConverter vrt2;
        private SingleAdjustmentMetrics vhal;
        private SingleAdjustmentMetrics vpal;
        private SingleAdjustmentMetrics halt;
        private SingleAdjustmentMetrics palt;
        private LigatureGlyphConverter liga;
        private PairAdjustmentMetrics kern;

        private void MainWindow_Loaded(object sender, System.Windows.RoutedEventArgs e)
        {
            var listEN = GetFontFiles();
            var listJP = GetJapaneseFontFiles();
            this.EnglishFontList.ItemsSource = listEN;
            this.EnglishFontList.SelectedIndex = 0;
            this.JapaneseFontList.ItemsSource = listJP;
            this.JapaneseFontList.SelectedIndex = 0;
            this.EnglishDemoText.Text = "Microsoft Office (Excel, Word, PowerPoint)";
            this.JapaneseDemoText.Text = "「こんにちは、いい天気ですね。」";
        }


        private void EnglishDemoText_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)
        {
            ShowEnglishText(this.EnglishDemoText.Text);
        }


        private void JapaneseDemoText_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)
        {
            ShowJapaneseText(this.JapaneseDemoText.Text);
        }


        private void ShowEnglishText(string text)
        {
            this.EnglishDemoView.Children.Clear();

            double emSize = 25;
            double x = 0;                                       //現在座標
            double y = 0;                                       //現在座標
            double outputX = 0;                                 //出力座標
            double outputY = 0;                                 //出力座標
            bool isKern = this.KerningCheck.IsChecked.Value;
            bool isLiga = this.LigatureCheck.IsChecked.Value;


            int ligatureSkip = 0;                               //合字によるスキップ判定
            bool nextKerning = false;                           //次の文字にカーニングを適用するか否か
            PositionInfo pinfo = new PositionInfo();            //現在の文字に適用する位置調整情報
            PositionInfo pinfoNext = new PositionInfo();        //次の文字に適用する位置調整情報
            bool needAdjust = false;                            //文字に適用する位置調整情報があるか否か

            x = 0;
            y = gtfEN.Baseline * emSize;

            for (int i = 0; i <= text.Length - 1; i++)
            {
                if (ligatureSkip > 0)
                {
                    ligatureSkip = ligatureSkip - 1;
                    continue;
                }

                char c = text[i];
                UInt16 gid = GetGlyphIndex(gtfEN, c);

                //合字探索
                if (isLiga && liga.HasLigature(gid))
                {
                    //現在のグリフを最初のグリフとする合字の最大文字数を取得
                    int maxLen = liga.GetLigatureMaxLength(gid);

                    //現在のグリフ以降の文字のグリフを最大文字数までリストに格納
                    List<UInt16> list = new List<UInt16>();
                    list.Add(gid);
                    for (int j = 1; j <= maxLen - 1; j++)
                    {
                        if (text.Length > i + j)
                        {
                            list.Add(GetGlyphIndex(gtfEN, text[i + j]));
                        }
                    }

                    if (list.Count > 1)
                    {
                        //合字の最小文字数である2文字までカウントダウンしながら合字可能か確認
                        for (int j = maxLen; j >= 2; j += -1)
                        {
                            if (liga.CanConvert(list.ToArray()))
                            {
                                gid = liga.Convert(list.ToArray());
                                ligatureSkip = list.Count - 1;
                            }
                            else
                            {
                                list.RemoveAt(list.Count - 1);
                            }
                        }
                    }

                }

                //カーニングチェック
                if ((ligatureSkip == 0) && isKern)
                {
                    char nextChar = '\0';
                    if (text.Length > i + 1)
                    {
                        nextChar = text[i + 1];
                    }
                    else
                    {
                        nextChar = new char();
                    }
                    UInt16 gid2 = GetGlyphIndex(gtfEN, nextChar);

                    //前のカーニングペアの位置調整を反映
                    if (nextKerning)
                    {
                        needAdjust = true;
                        nextKerning = false;
                        pinfo = pinfoNext;
                        pinfoNext = new PositionInfo();
                    }

                    //現在のカーニングペアの位置調整を反映
                    if (kern.HasAdjustment(gid, gid2))
                    {
                        needAdjust = true;
                        nextKerning = true;
                        pinfo = AddPositionInfo(pinfo, kern.GetFirstAdjustment(gid, gid2));
                        pinfoNext = kern.GetSecondAdjustment(gid, gid2);
                    }
                }

                if (needAdjust)
                {
                    outputX = x + (pinfo.XPlacement * emSize);
                    outputY = y - (pinfo.YPlacement * emSize);
                }
                else
                {
                    outputX = x;
                    outputY = y;
                }

                Geometry geo = gtfEN.GetGlyphOutline(gid, emSize, emSize);
                Path p = new Path();
                p.Data = geo;
                p.StrokeThickness = 0;
                p.Fill = new SolidColorBrush(Colors.Black);
                this.EnglishDemoView.Children.Add(p);
                p.SetValue(Canvas.LeftProperty, outputX);
                p.SetValue(Canvas.TopProperty, outputY);

                if (needAdjust)
                {
                    x = (x + (gtfEN.AdvanceWidths[gid] * emSize)) + (pinfo.XAdvance * emSize);
                    pinfo = new PositionInfo();
                    needAdjust = false;
                }
                else
                {
                    x = x + (gtfEN.AdvanceWidths[gid] * emSize);
                }

                if (x > this.EnglishDemoView.ActualWidth)
                {
                    x = 0;
                    y = y + emSize;
                }
            }
        }


        private void ShowJapaneseText(string text)
        {
            this.JapaneseDemoView.Children.Clear();

            double emSize = 25;
            double x = 0;                                       //現在座標
            double y = 0;                                       //現在座標
            double outputX = 0;                                 //出力座標
            double outputY = 0;                                 //出力座標
            bool isVert = this.VerticalCheck.IsChecked.Value | this.AdvancedVerticalCheck.IsChecked.Value;
            bool isPalt = this.ProportionalCheck.IsChecked.Value;
            bool isHalt = this.HalfCheck.IsChecked.Value;

            PositionInfo pinfo = new PositionInfo();            //現在の文字に適用する位置調整情報
            bool needAdjust = false;                            //文字に適用する位置調整情報があるか否か

            //縦書きの場合は右上の座標を基点とします。
            if (isVert)
            {
                x = this.JapaneseDemoView.ActualWidth - emSize;
                y = gtfJP.Baseline * emSize;
            }
            else
            {
                x = 0;
                y = gtfJP.Baseline * emSize;
            }

            for (int i = 0; i <= text.Length - 1; i++)
            {
                char c = text[i];
                UInt16 gid = GetGlyphIndex(gtfJP, c);

                //表示位置の調整
                if (isVert)
                {
                    //縦書き用のグリフに変換
                    if (this.AdvancedVerticalCheck.IsChecked.Value)
                    {
                        gid = vrt2.Convert(gid);
                    }
                    else if (this.VerticalCheck.IsChecked.Value)
                    {
                        gid = vert.Convert(gid);
                    }

                    //縦書き用の半角幅またはプロポーショナルメトリクスの取得
                    if (isPalt && vpal.HasAdjustment(gid))
                    {
                        pinfo = vpal.GetAdjustment(gid);
                        needAdjust = true;
                    }
                    else if (isHalt && vhal.HasAdjustment(gid))
                    {
                        pinfo = vhal.GetAdjustment(gid);
                        needAdjust = true;
                    }
                }
                else
                {
                    //横書き用の半角幅またはプロポーショナルメトリクスの取得
                    if (isPalt && palt.HasAdjustment(gid))
                    {
                        pinfo = palt.GetAdjustment(gid);
                        needAdjust = true;
                    }
                    else if (isHalt && halt.HasAdjustment(gid))
                    {
                        pinfo = halt.GetAdjustment(gid);
                        needAdjust = true;
                    }
                }

                if (needAdjust)
                {
                    outputX = x + (pinfo.XPlacement * emSize);
                    outputY = y - (pinfo.YPlacement * emSize);
                }
                else
                {
                    outputX = x;
                    outputY = y;
                }

                Geometry geo = gtfJP.GetGlyphOutline(gid, emSize, emSize);
                Path p = new Path();
                p.Data = geo;
                p.StrokeThickness = 0;
                p.Fill = new SolidColorBrush(Colors.Black);
                this.JapaneseDemoView.Children.Add(p);
                p.SetValue(Canvas.LeftProperty, outputX);
                p.SetValue(Canvas.TopProperty, outputY);

                if (isVert)
                {
                    if (needAdjust)
                    {
                        y = (y + (gtfJP.AdvanceHeights[gid] * emSize)) + (pinfo.YAdvance * emSize);
                        pinfo = new PositionInfo();
                        needAdjust = false;
                    }
                    else
                    {
                        y = y + (gtfJP.AdvanceHeights[gid] * emSize);
                    }
                }
                else
                {
                    if (needAdjust)
                    {
                        x = (x + (gtfJP.AdvanceWidths[gid] * emSize)) + (pinfo.XAdvance * emSize);
                        pinfo = new PositionInfo();
                        needAdjust = false;
                    }
                    else
                    {
                        x = x + (gtfJP.AdvanceWidths[gid] * emSize);
                    }
                }

                if (isVert)
                {
                    if (y > this.JapaneseDemoView.ActualHeight)
                    {
                        x = x - emSize;
                        y = gtfJP.Baseline * emSize;
                    }
                }
                else
                {
                    if (x > this.JapaneseDemoView.ActualWidth)
                    {
                        x = 0;
                        y = y + emSize;
                    }
                }
            }
        }

        private UInt16 GetGlyphIndex(GlyphTypeface gtf, char c)
        {
            int code = (int)c;
            if (gtf.CharacterToGlyphMap.ContainsKey(code))
            {
                return gtf.CharacterToGlyphMap[code];
            }
            else if (gtf.CharacterToGlyphMap.ContainsKey((int)'?'))
            {
                return gtf.CharacterToGlyphMap[(int)'?'];
            }
            else
            {
                return 0;
            }
        }

        private PositionInfo AddPositionInfo(PositionInfo pinfo1, PositionInfo pinfo2)
        {
            PositionInfo pinfo = new PositionInfo();
            pinfo.XAdvance = pinfo1.XAdvance + pinfo2.XAdvance;
            pinfo.YAdvance = pinfo1.YAdvance + pinfo2.YAdvance;
            pinfo.XPlacement = pinfo1.XPlacement + pinfo2.XPlacement;
            pinfo.YPlacement = pinfo1.YPlacement + pinfo2.YPlacement;
            return pinfo;
        }

        private void EnglishCheckBox_Click(object sender, System.Windows.RoutedEventArgs e)
        {
            ShowEnglishText(this.EnglishDemoText.Text);
        }

        private void JapaneseCheckBox_Click(object sender, System.Windows.RoutedEventArgs e)
        {
            ShowJapaneseText(this.JapaneseDemoText.Text);
        }
        public MainWindow()
        {
            Loaded += MainWindow_Loaded;
        }

        private SortedList<string, Uri> GetJapaneseFontFiles()
        {
            var temp = GetFontFiles();
            var result = new SortedList<string, Uri>();

            foreach (string key in temp.Keys)
            {
                var gtf = new GlyphTypeface(temp[key]);
                if (gtf.GlyphCount >= 8284)
                {
                    result.Add(key, temp[key]);
                }
            }
            return result;
        }

        private SortedList<string, Uri> GetFontFiles()
        {
            var result = new SortedList<string, Uri>();

            string fontDir = Environment.GetFolderPath(Environment.SpecialFolder.Fonts);
            
            //TrueType
            foreach (string filePath in System.IO.Directory.GetFiles(fontDir, "*.ttf"))
            {
                result.Add(System.IO.Path.GetFileName(filePath), new Uri(filePath));
            }

            //OpenType
            foreach (string filePath in System.IO.Directory.GetFiles(fontDir, "*.otf"))
            {
                result.Add(System.IO.Path.GetFileName(filePath), new Uri(filePath));
            }

            //TrueTypeCollection
            foreach (string filePath in System.IO.Directory.GetFiles(fontDir, "*.ttc"))
            {
                using (var fs = new System.IO.FileStream(filePath, System.IO.FileMode.Open, System.IO.FileAccess.Read))
                {
                    for (int i = 0; i < TypefaceInfo.GetCollectionCount(fs); i++)
                    {
                        string collectionPath = filePath + "#" + i.ToString();
                        var collectionUri = new UriBuilder("file", "", -1, filePath, "#" + i.ToString()).Uri;
                        result.Add(System.IO.Path.GetFileName(collectionPath), collectionUri);
                    }
                }
            }
            return result;
        }

        private void EnglishFontList_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            Uri uri = ((KeyValuePair<string, Uri>)this.EnglishFontList.SelectedItem).Value;
            int num = uri.Fragment == "" ? 0 : int.Parse(uri.Fragment.Replace("#", ""));
            this.gtfEN = new GlyphTypeface(uri);
            TypefaceInfo info = new TypefaceInfo(this.gtfEN.GetFontStream(), num);
            liga = info.GetLigatureGlyphConverter();
            kern = info.GetKerningAdjustmentMetrics();

            this.LigatureCheck.IsEnabled = (liga.Count != 0);
            this.KerningCheck.IsEnabled = (kern.Count != 0);

            ShowEnglishText(this.EnglishDemoText.Text);
        }

        private void JapaneseFontList_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            Uri uri = ((KeyValuePair<string, Uri>)this.JapaneseFontList.SelectedItem).Value;
            int num = uri.Fragment == "" ? 0 : int.Parse(uri.Fragment.Replace("#", ""));
            this.gtfJP = new GlyphTypeface(uri);
            TypefaceInfo info = new TypefaceInfo(this.gtfJP.GetFontStream(), num);
            vert = info.GetVerticalGlyphConverter();
            vrt2 = info.GetAdvancedVerticalGlyphConverter();
            vhal = info.GetHalfAdjustmentMetrics(true);
            vpal = info.GetProportionalAdjustmentMetrics(true);
            halt = info.GetHalfAdjustmentMetrics(false);
            palt = info.GetProportionalAdjustmentMetrics(false);

            this.VerticalCheck.IsEnabled = (vert.Count != 0);
            this.AdvancedVerticalCheck.IsEnabled = (vrt2.Count != 0);
            this.HalfCheck.IsEnabled = (halt.Count != 0);
            this.ProportionalCheck.IsEnabled = (palt.Count != 0);

            ShowJapaneseText(this.JapaneseDemoText.Text);
        }
    }
}
